// Extract.cpp

#include "StdAfx.h"

#include <stdio.h>

#include "Common/FileSystem/FileDir.h"
#include "Common/MS/PropVariant.h"
#include "Common/String/DataConversions.h"

#include "Common/FileSystem/ExtractingFilePath.h"

#include "Extract.h"
#include "Common/Props/SetProperties.h"
#include "Common/FileSystem/SortUtils.h"


namespace Common
{
	using namespace FileSystem;
namespace Archive
{
static HRESULT DecompressArchive(
    const CArc * const pArc,
    UINT64 packSize,
    const Wildcard::CCensorNode &wildcardCensor,
    const CExtractOptions &options,
    IExtractCallbackUI *callback,
    CArchiveExtractCallback *extractCallbackSpec,
    CString &errorMessage,
    UINT64 &stdInProcessed)
{
	ATLASSERT(NULL != pArc);
	stdInProcessed = 0;
  IInArchive *archive = pArc->Archive;
  std::vector<UINT32> realIndices;
  HRESULT hRes (S_OK);
  if (!options.StdInMode)
  {
    UINT32 numItems;
    hRes = archive->GetNumberOfItems(&numItems);
	if(S_OK != hRes)
		return hRes;
    
    for (UINT32 i = 0; i < numItems; i++)
    {
      CString filePath;
      hRes = pArc->GetItemPath(i, filePath);
	  if(S_OK != hRes)
		return hRes;
      bool isFolder;
      hRes = IsArchiveItemFolder(archive, i, isFolder);
	  if(S_OK != hRes)
		return hRes;
      if (!wildcardCensor.CheckPath(filePath, !isFolder))
        continue;
      realIndices.push_back(i);
    }
    if (realIndices.size() == 0)
    {
      callback->ThereAreNoFiles();
      return S_OK;
    }
  }

  CStringVector removePathParts;

  CString outDir = options.OutputDir;
  outDir.Replace(_T("*"), GetCorrectFsPath(pArc->DefaultName));
  #ifdef _WIN32
  // GetCorrectFullFsPath doesn't like "..".
  // outDir.TrimRight();
  // outDir = GetCorrectFullFsPath(outDir);
  #endif

  if (!outDir.IsEmpty())
    if (!Common::FileSystem::CreateComplexDirectory(outDir))
    {
      hRes = ::GetLastError();
      if (S_OK == hRes)
        hRes = E_FAIL;
      errorMessage = ((CString)_T("Can not create output directory ")) + outDir;
      return hRes;
    }

  extractCallbackSpec->Init(
      options.StdInMode ? &wildcardCensor : NULL,
      pArc,
      callback,
      options.StdOutMode, options.TestMode, options.CalcCrc,
      outDir,
      removePathParts,
      packSize);

  #if !defined(_7ZIP_ST) && !defined(_SFX)
  hRes = SetProperties(archive, options.Properties);
  if(S_OK != hRes)
	return hRes;
  #endif

  INT32 testMode = (options.TestMode && !options.CalcCrc) ? 1: 0;
  if (options.StdInMode)
  {
    hRes = archive->Extract(NULL, (UINT32)(INT32)-1, testMode, extractCallbackSpec);
    CComPropVariant prop;
    if (archive->GetArchiveProperty(kpidPhySize, &prop) == S_OK)
      if (prop.vt == VT_UI8 || prop.vt == VT_UI4)
        stdInProcessed = String::ConvertPropVariantToUInt64(prop);
  }
  else
    hRes = archive->Extract(&realIndices.front(), realIndices.size(), testMode, extractCallbackSpec);

  return callback->ExtractResult(hRes);
}

HRESULT DecompressArchives(
    CCodecs *codecs, const std::vector<int> &formatIndices,
    std::vector<CString> &arcPaths, std::vector<CString> &arcPathsFull,
    const Wildcard::CCensorNode &wildcardCensor,
    const CExtractOptions &options,
    IOpenCallbackUI *openCallback,
    IExtractCallbackUI *extractCallback,
    CString &errorMessage,
    CDecompressStat &stat)
{
  stat.Clear();
  size_t i;
  UINT64 totalPackSize = 0;
  std::vector<UINT64> archiveSizes;

  size_t numArcs = options.StdInMode ? 1 : arcPaths.size();

  archiveSizes.reserve(numArcs);

  for (i = 0; i < numArcs; i++)
  {
    Common::FileSystem::File::CFileInfo fi;
    fi.Size = 0;
    if (!options.StdInMode)
    {
      const CString &arcPath = arcPaths[i];
      if (!fi.Find(arcPath))
        throw "there is no such archive";
      if (fi.IsDir())
        throw "can't decompress folder";
    }
    archiveSizes.push_back(fi.Size);
    totalPackSize += fi.Size;
  }
  CArchiveExtractCallback *extractCallbackSpec = new CComObject<CArchiveExtractCallback>;
  extractCallbackSpec->FinalConstruct();
  CComPtr<IArchiveExtractCallback> ec(extractCallbackSpec);
  bool multi = (numArcs > 1);
  extractCallbackSpec->InitForMulti(multi, options.PathMode, options.OverwriteMode);
  HRESULT hRes (S_OK);
  if (multi)
  {
    hRes = extractCallback->SetTotal(totalPackSize);
	if(S_OK != hRes)
		return hRes;
  }
  for (i = 0; i < numArcs; i++)
  {
    const CString &arcPath = arcPaths[i];
    FileSystem::File::CFileInfo fi;
    if (options.StdInMode)
    {
      fi.Size = 0;
      fi.Attributes = 0;
    }
    else
    {
      if (!fi.Find(arcPath) || fi.IsDir())
        throw "there is no such archive";
    }

    #ifndef _NO_CRYPTO
    openCallback->Open_ClearPasswordWasAskedFlag();
    #endif

    hRes = extractCallback->BeforeOpen(arcPath);
	if(S_OK != hRes)
		return hRes;
    CArchiveLink archiveLink;

    std::vector<int> formatIndices2 = formatIndices;
    #ifndef _SFX
	if (formatIndices.empty())
    {
      int pos = arcPath.ReverseFind(_T('.'));
      if (pos >= 0)
      {
        CString s = arcPath.Mid(pos + 1);
        size_t index = codecs->FindFormatForExtension(s);
        if (index != -1 && s == _T("001"))
        {
          s = arcPath.Left(pos);
          pos = s.ReverseFind(_T('.'));
          if (pos >= 0)
          {
            size_t index2 = codecs->FindFormatForExtension(s.Mid(pos + 1));
            if (index2 != -1 && s.CompareNoCase(_T("rar")) != 0)
            {
				formatIndices2.push_back(index2);
				formatIndices2.push_back(index);
            }
          }
        }
      }
    }
    #endif
    HRESULT result = archiveLink.Open2(codecs, formatIndices2, options.StdInMode, NULL, arcPath, openCallback);
    if (result == E_ABORT)
      return result;

    bool crypted = false;
    #ifndef _NO_CRYPTO
    crypted = openCallback->Open_WasPasswordAsked();
    #endif

    hRes = extractCallback->OpenResult(arcPath, result, crypted);
	if(S_OK != hRes)
		return hRes;
    if (result != S_OK)
      continue;

    if (!options.StdInMode)
	{
		for (size_t v = 0; v < archiveLink.VolumePaths.size(); v++)
		{
			std::vector<CString>::iterator it = std::find(arcPathsFull.begin(), arcPathsFull.end(),  archiveLink.VolumePaths[v]);
		  //int index = arcPathsFull.FindInSorted(archiveLink.VolumePaths[v]);
			//int index = arcPathsFull.
		  //if (index >= 0 && index > i)
			if(it != arcPathsFull.end())
		  {
			 size_t index =  std::distance(arcPathsFull.begin(), it);
			 arcPaths.erase(arcPaths.begin() + index);// Delete(index);
			 arcPathsFull.erase(it);
			totalPackSize -= archiveSizes[index];
			archiveSizes.erase(archiveSizes.begin() + index);
			numArcs = arcPaths.size();
		  }
		}
	}
    if (archiveLink.VolumePaths.size() != 0)
    {
      totalPackSize += archiveLink.VolumesSize;
      hRes = extractCallback->SetTotal(totalPackSize);
	  if(S_OK != hRes)
		return hRes;
    }

    #ifndef _NO_CRYPTO
    CString password;
    hRes = openCallback->Open_GetPasswordIfAny(password);
	if(S_OK != hRes)
		return hRes;
    if (!password.IsEmpty())
    {
      hRes =  extractCallback->SetPassword(password);
	  if(S_OK != hRes)
		return hRes;
    }
    #endif

    for (size_t v = 0; v < archiveLink.Arcs.size(); v++)
    {
      const CString &s = archiveLink.Arcs[v]->ErrorMessage;
      if (!s.IsEmpty())
      {
        hRes = extractCallback->MessageError(s);      
		if(S_OK != hRes)
			return hRes;
	  }
    }

	CArc * pArc = archiveLink.Arcs.back().get();
    pArc->MTimeDefined = (!options.StdInMode && !fi.IsDevice);
	pArc->MTime = fi.ModificationTime;

    UINT64 packProcessed;
    hRes = DecompressArchive(pArc,
        fi.Size + archiveLink.VolumesSize,
        wildcardCensor, options, extractCallback, extractCallbackSpec, errorMessage, packProcessed);
	if(S_OK != hRes)
		return hRes;
    if (!options.StdInMode)
      packProcessed = fi.Size + archiveLink.VolumesSize;
    extractCallbackSpec->LocalProgressSpec->InSize += packProcessed;
    extractCallbackSpec->LocalProgressSpec->OutSize = extractCallbackSpec->UnpackSize;
    if (!errorMessage.IsEmpty())
      return E_FAIL;
  }
  stat.NumFolders = extractCallbackSpec->NumFolders;
  stat.NumFiles = extractCallbackSpec->NumFiles;
  stat.UnpackSize = extractCallbackSpec->UnpackSize;
  stat.CrcSum = extractCallbackSpec->CrcSum;

  stat.NumArchives = arcPaths.size();
  stat.PackSize = extractCallbackSpec->LocalProgressSpec->InSize;
  return S_OK;
}
}
}